Раздел: Идеи и принципы программирования

Третий уровень сложности (на оценку «Отлично»)

1. Рекурсивная агрегация экземпляров класса.
* Ответ: Это ситуация, когда объект содержит в качестве атрибутов ссылки на другие объекты того же самого класса.
* Смысл: Позволяет строить древовидные структуры данных (родословные, деревья каталогов).
* Пример:

    class Person:
        def __init__(self, name, mother=None, father=None):
            self.name = name
            self.mother = mother # Ссылка на такой же класс Person
            self.father = father # Ссылка на такой же класс Person

    p1 = Person("Отец")
    p2 = Person("Мать")
    p3 = Person("Сын", mother=p2, father=p1)
    # p3.father.name -> "Отец"

2. Абстрактные базовые классы.
* Ответ: Классы, которые не предназначены для создания экземпляров. Они служат шаблонами (контрактами) для классов-наследников, обязывая их реализовать определенные методы.
* Реализация: Используется библиотека abc (Abstract Base Classes) и декоратор @abstractmethod.

3. Метаклассы.
* Ответ: Это «классы классов». Класс является экземпляром метакласса. Метаклассы позволяют управлять процессом создания самих классов (например, автоматически регистрировать их в списке).
* Код из Лекции 13 (автоматическая регистрация экземпляров):

    # Метакласс
    class metalist(type):
        def __init__(cls, *args):
            cls.L = [] # При создании класса создаем у него список L

    # Класс, использующий метакласс
    class Person(metaclass=metalist):
        def __init__(self, name):
            self.name = name
            # self.__class__.L — это доступ к списку, созданному метаклассом
            self.__class__.L.append(self)

    p1 = Person("Ivan")
    p2 = Person("Maria")
    # Список Person.L заполнился автоматически
    print(Person.L) # Выведет список объектов p1 и p2

4. Интроспекция.
* Ответ: Способность программы исследовать саму себя во время выполнения: узнавать тип объекта, его атрибуты, имя класса и т.д.
* Инструменты: type(), id(), dir(), getattr(), словари __dict__, globals(), locals().

5. Итераторы.
* Ответ: Объекты, предназначенные для перебора элементов коллекции.
* Протокол итератора:
1. __iter__(): возвращает объект итератора (обычно self).
2. __next__(): возвращает следующий элемент или вызывает StopIteration.
* Код из Лекции 10 (Бесконечное кольцо):

    class Ring(list):
        def __iter__(self):
            self.ind = 0 # Сбрасываем счетчик при начале перебора
            return self

        def __next__(self):
            # Реализуем логику: перебор через один элемент или бесконечно по кругу
            res = self[self.ind % len(self)] # Берем элемент по модулю
            self.ind += 1
            return res

    R = Ring([1, 2, 3])
    iterator = iter(R)
    print(next(iterator)) # 1
    print(next(iterator)) # 2
    # ...и так далее бесконечно

6. Сопрограммы (Корутины).
* Ответ: Функции, которые могут приостанавливать выполнение и не только отдавать (yield), но и принимать данные извне в процессе работы. Основа асинхронности.
* Код из Лекции 11 (Вычисление среднего арифметического на лету):

    def avg():
        s = 0
        c = 0
        r = None
        while True:
            # yield справа: программа ждет, пока ей пришлют число в 'a'
            # и отдает текущий результат 'r'
            a = yield r
            s += a
            c += 1
            r = s / c

    A = avg()
    next(A) # "Прогреваем" сопрограмму (доходим до первого yield)
    print(A.send(10)) # Шлем 10 -> ср.знач 10.0
    print(A.send(20)) # Шлем 20 -> ср.знач 15.0 ( (10+20)/2 )

7. Хэш-функция.
* Ответ: Функция, преобразующая объект в число. Необходима для того, чтобы объект мог быть ключом в словаре (dict) или элементом множества (set).
* Проблема: По умолчанию списки (list) не хешируемы. Чтобы использовать список как ключ, нужно написать класс-обертку.
* Код из Лекции 12 (Список как ключ словаря):

    class hlist(list):
        # Хэш-функция: превращаем список в уникальное число
        # (как перевод из двоичной системы, если список из 0 и 1)
        def __hash__(self):
            s = 0
            for i, val in enumerate(self):
                s += val * (2**i)
            return s

        # Обязательно нужен метод сравнения
        def __eq__(self, other):
            return list(self) == list(other)

    # Теперь можно использовать как ключ:
    D = { hlist([1, 0, 1]): "Key A", hlist([0, 1]): "Key B" }
    print(D[hlist([1, 0, 1])]) # Выведет "Key A"

8. Композиция функций.
* Ответ: Применение одной функции к результату другой: $f(g(x))$.
* В лекциях: Реализовано через перегрузку оператора умножения __mul__ в классе-декораторе, что позволяет писать (fact * fib)(x).

9. Отображения, сохраняющие структуру (Функторы).
* Ответ: Применение функции к элементам внутри контейнера без изменения структуры контейнера (например, map для списков).

10. Идея программирования как композиции отображений.
* Ответ: Подход, где программа строится как цепочка преобразований данных, проходящих через функции («стрелочки»), сохраняющие структуру данных.

11. Ленивые вычисления: формулы, индексы, атрибуты.
* Ответ (Лекция 13): Стратегия, при которой вычисления откладываются до момента, когда их результат реально понадобится.
* Ленивые формулы: Классы var и expr. Мы пишем c = a + b, но сложение происходит не сразу. Сложение происходит только при вызове c() или print(c). Строится "дерево выражений".
* Ленивые индексы: В A[x > 5] выражение x > 5 не вычисляется сразу в True/False. Оно передается как функция/выражение внутри индексатора __getitem__, и там применяется к каждому элементу.
* Ленивые атрибуты: Использование @property. Значение атрибута вычисляется в момент обращения к нему (через точку), а не хранится в памяти постоянно.

← Меню